#ifndef _COMBO_PROCEDURE_REPOSITORY_H
#define _COMBO_PROCEDURE_REPOSITORY_H

#include <LADSUtil/exceptions.h>
#include <LADSUtil/Logger.h>
#include "ComboReduct/combo/procedure_call.h"
#include "ComboReduct/combo/vertex.h"

#include <set>
#include <map>

#define LINE_CHAR_MAX 4096

namespace combo {
  
  //def for procedure_repository
  typedef std::map<std::string, procedure_call_base*> str_proc_map;
  typedef str_proc_map::const_iterator str_proc_map_const_it;
  typedef str_proc_map::iterator str_proc_map_it;

  class procedure_repository {
  protected:
    str_proc_map _repo;

    //for all definite_objects in tr, check if the string matches the name
    //of a procedure_call and replace it by it if so
    void instantiate_procedure_calls(vtree& tr,
				     bool warn_on_definite_object=false) const;

  public:
    //each new procedure instanciated using procedure_call_base constructor
    //is added into the following static repository
    //to avoid ambiguities 2 procedures with the asame name won't be allowed
    //and a cassert will be raised
    void add(procedure_call_base* pc);

    //remove the procedure of a given name if any
    void remove(const std::string& name);

    //function allowing to retreive a procedure based on it's name
    //if no procedure corresponds to that name then it returns a NULL pointer
    procedure_call instance(const std::string& name) const;

    //return true if the repository contains a procedure with the given name
    bool does_contain(const std::string& name) const;

    //apply instantiate_procedure_calls on the entire repository
    void instantiate_procedure_calls(bool warn_on_definite_object=false);

    //infer all unknown procedure types of the repository,
    //returns false if exists type error
    //the procedures with type error will have ill_formed types
    bool infer_types_repo();

    //clear
    void clear();

    //output stream method
    std::ostream& toStream(std::ostream& out, bool with_type = false) const;

    //for debugging print the repository on the standard output
    void print(bool with_type = false) const;
  };
  
}//~namespace combo

template<class BUILTIN_ACTION,
	 class PERCEPTION,
	 class ACTION_SYMBOL,
	 class INDEFINITE_OBJECT>
unsigned int load_procedure_repository(std::istream& in,
				       combo::procedure_repository& pr,
				       bool type_checking = false) {
  unsigned int n = 0;
  
  while (in.good()) {
    while (in.peek()==' ' || in.peek()=='\n' || in.peek()=='\t')
      in.get();
    
    if(in.peek()=='#') { //a comment line
      char tmp[LINE_CHAR_MAX];
      in.getline(tmp,LINE_CHAR_MAX);
      continue;
    }
    
    combo::procedure_call pc = load_procedure_call<BUILTIN_ACTION, PERCEPTION,
      ACTION_SYMBOL, INDEFINITE_OBJECT>(in, false);
    
    if (!in.good()){
      break;
    }
    
    if(pc) {
      pr.add(const_cast<combo::procedure_call_base*>(pc));
      ++n;
      MAIN_LOGGER.log(LADSUtil::Logger::FINE, 
		      "procedure_repository - Loaded '%s' with arity '%d'.", 
		      pc->get_name().c_str(), pc->arity());
      
    } else {
      MAIN_LOGGER.log(LADSUtil::Logger::ERROR, 
		      "procedure_repository - Error parsing combo function.");
    }
  }
  //doing the resolution and type checking here
  //allows mutual recursion amongst functions to be defined in the input
  pr.instantiate_procedure_calls(true);
  if(type_checking) {
    bool type_check_success = pr.infer_types_repo();
    if(!type_check_success) {
      MAIN_LOGGER.log(LADSUtil::Logger::ERROR, 
		      "procedure_repository - Error type checking.");      
    }
  }
  return n;
}

#endif

